package com.qi.qipay.commons.service;

import com.qi.protocol.annotations.ApiDefinition;
import com.qi.protocol.annotations.ApiService;
import com.qi.qipay.commons.utils.ClassTypeUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import sun.reflect.misc.MethodUtil;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class ServiceContainer {
    private final Map<String, IService> serviceMap = new HashMap<>();
    private final Map<String, Map<String, Method>> methodMap = new HashMap<>();
    private static ServiceContainer INSTANCE = null;

    private ServiceContainer(ApplicationContext applicationContext) {
        Map<String, IService> serviceMap = applicationContext.getBeansOfType(IService.class);
        for (Map.Entry<String, IService> entry : serviceMap.entrySet()) {
            List<ApiService> names = findApiService(entry.getValue());
            for (ApiService name : names) {
                this.serviceMap.put(name.value(), entry.getValue());
                this.methodMap.put(name.value(), findApiMethods(entry.getValue(), name));
            }
        }
    }

    private Map<String, Method> findApiMethods(IService iService, ApiService name) {
        Map<String, Method> result = new HashMap<>();
        Class<?> clazz = getServiceInterfaceByServiceName(iService, name);
        assert clazz != null;
        Method[] methods = MethodUtil.getMethods(clazz);
        for (Method method : methods) {
            ApiDefinition apiDefinition = AnnotationUtils.getAnnotation(method, ApiDefinition.class);
            if (apiDefinition == null) {
                throw new RuntimeException("In " + clazz.getName() + " Class the " + method.getName() + " Method is not define @ApiDefinition annotation");
            }
            if (apiDefinition.enable()) {
                result.put(apiDefinition.name(), method);
            }
        }
        return result;
    }

    private Class<?> getServiceInterfaceByServiceName(IService iService, ApiService name) {
        Class<?>[] clazzs = ClassUtils.getAllInterfaces(iService);
        for (Class<?> clazz : clazzs) {
            if (ClassUtils.isAssignable(IService.class, clazz)) {
                ApiService apiService = AnnotationUtils.findAnnotation(clazz, ApiService.class);
                if (null == apiService) {
                    throw new RuntimeException("The " + clazz.getName() + " is not define @ApiService annotation");
                }
                if (apiService.value().equals(name.value())) {
                    return clazz;
                }
            }
        }
        return null;
    }

    private List<ApiService> findApiService(IService iService) {
        List<ApiService> apiServices = new ArrayList<>();
        Class<?>[] clazzs = ClassUtils.getAllInterfaces(iService);
        for (Class<?> clazz : clazzs) {
            if (ClassUtils.isAssignable(IService.class, clazz)) {
                apiServices.add(AnnotationUtils.findAnnotation(clazz, ApiService.class));
            }
        }
        return apiServices;
    }

    public static ServiceContainer init(ApplicationContext applicationContext) {
        if (INSTANCE == null) {
            synchronized (ServiceContainer.class) {
                if (INSTANCE == null && applicationContext != null)
                    INSTANCE = new ServiceContainer(applicationContext);
            }
        }
        return INSTANCE;
    }

    public static ServiceContainer getInstance() {
        Assert.notNull(INSTANCE, "服务容器尚未初始化，请先调用init方法进行初始化");
        return INSTANCE;
    }

    /**
     * 反射执行service方法
     *
     * @param clazz
     * @param serviceName
     * @param methodName
     * @param paramMap
     * @param <T>
     * @return
     */
    public static <T extends IService> Object invokeService(Class<T> clazz, String serviceName, String methodName,
                                                            Map<String, Object> paramMap) throws InvocationTargetException, IllegalAccessException {
        IService service = getInstance().serviceMap.get(serviceName);
        Method invokeMethod = getInvokeMethod(serviceName, methodName);
        Object[] args = getParamsByMethodAndParamMap(invokeMethod, paramMap);
        return invokeMethod.invoke(service, args);
    }

    private static Object[] getParamsByMethodAndParamMap(Method invokeMethod, Map<String, Object> paramMap) {
        ApiDefinition apiDefinition = invokeMethod.getAnnotation(ApiDefinition.class);
        String[] inputParamNames = apiDefinition.input();
        Class<?>[] parameterTypes = invokeMethod.getParameterTypes();
        Object[] result = new Object[inputParamNames.length];
        for (int i = 0; i < inputParamNames.length; i++) {
            result[i] = ClassTypeUtil.convertToTargetClass(paramMap.get(inputParamNames[i]), parameterTypes[i]);
        }
        return result;
    }


    private static Method getInvokeMethod(String serviceName, String methodName) {
        Map<String, Method> methodMap = getInstance().methodMap.get(serviceName);
        return methodMap.get(methodName);
    }

}
